/*****************************************************************************
*
*  PROJECT:     SlipStream
*               (CGame - Game Source File)
*  LICENSE:     GNU General Public License v3 - http://www.gnu.org/licenses/gpl.html

*  DEVELOPERS:  Philip Farquharson	<B00137077@studentmail.uws.ac.uk>
*				John Gray			<B00145524@studentmail.uws.ac.uk>		
*				Zohaib Bhatti		<B00104280@studentmail.uws.ac.uk>
*				Euan Hislop			<B00138636@studentmail.uws.ac.uk>
*
*****************************************************************************/
#include "CGame.h"

// static initialization
CKeyBindManager		CGame::m_KeyBindManager;
CTextureManager		CGame::m_TextureManager;
CTimerManager		CGame::m_TimerManager;
CSoundManager		CGame::m_SoundManager;
CSkybox				CGame::m_SkyBox;
CCamera				CGame::m_Camera;
MD5Model			CGame::m_Player;
CLighting			CGame::m_Lighting;
reflections			CGame::m_Reflections;
CParticleEmitter*	CGame::m_pParticleEmitter1;
CParticleEmitter*	CGame::m_pParticleEmitter2;
bumpMapping			CGame::m_BumpMapping;
shadows				CGame::m_Shadows;

// Text on screen
CTextManager CGame::m_TextManager;
CTextObject* CGame::m_pTextFPS;
CTextObject* CGame::m_pTextPhysics;
CTextObject* CGame::m_pTextPhysicsCollisions;
CTextObject* CGame::m_pTextFullscreen;
CTextObject* CGame::m_pTextCameraPosition;
CTextObject* CGame::m_pTextControlsCamera;
CTextObject* CGame::m_pTextControlsShadows;
CTextObject* CGame::m_pTextControlsBumpmap;
CTextObject* CGame::m_pTextControlsPhysics;

CSound3D* CGame::m_pMusic;

// Physics
dWorldID CGame::m_ODEWorld;
dSpaceID CGame::m_ODESpace;
dGeomID CGame::m_floorPlane;
dGeomID CGame::m_roofPlane;
dGeomID CGame::m_wallPlaneNorth;
dGeomID CGame::m_wallPlaneEast;
dGeomID CGame::m_wallPlaneSouth;
dGeomID CGame::m_wallPlaneWest;
dJointGroupID CGame::m_ODEJointGroup;
std::list<cOdeObject*> CGame::m_ObjList;
cTimer CGame::m_phyTimer;
int CGame::m_cCollisions = 0;
#define PHYSICS_OBJECT_LIMIT	15

// Font
GLuint CGame::fontBase;

// Loading
CLoader* CGame::m_pLoader = NULL;
bool CGame::m_bLoaded = false;

CTimer* CGame::tmrFPSLimiter;
DWORD CGame::m_dwCurrentTime = 0;
DWORD CGame::m_dwFPSLimit = DEFAULT_FPS_LIMIT;
DWORD CGame::m_dwFPS = 0;
DWORD CGame::m_dwLastFPS = DEFAULT_FPS_LIMIT;

HWND CGame::hWnd;
HDC CGame::hDC;

int CGame::m_iReturnValue;
unsigned int CGame::m_uiLoadTime;

template<> CGame * CSingleton< CGame >::m_pSingleton = NULL;

// Open Dynamics Engine
#ifdef _DEBUG
	#pragma comment (lib, "ODE/ode_singled.lib")
#else
	#pragma comment (lib, "ODE/ode_single.lib")
#endif

int CGame::init ( HDC hDC )
{
	unsigned int uiStartTick = GET_TICK();
	this->hDC = hDC;

	m_pLoader = new CLoader(hDC);
	m_pLoader->SetProgress(5);

	// Bind our keys
	m_KeyBindManager.bindKey ( Exit, CKeyBind::VK_ESC, CKeyBind::BIND_TOGGLE);
	m_KeyBindManager.bindKey ( wKeyPressed, CKeyBind::VK_W, CKeyBind::BIND_SIMPLE); // W Key Bind moves camera forwards
	m_KeyBindManager.bindKey ( sKeyPressed, CKeyBind::VK_S, CKeyBind::BIND_SIMPLE);	// S Key Bind moves camera backwards
	m_KeyBindManager.bindKey ( aKeyPressed, CKeyBind::VK_A, CKeyBind::BIND_SIMPLE);	// A Key Bind moves camera left
	m_KeyBindManager.bindKey ( dKeyPressed, CKeyBind::VK_D, CKeyBind::BIND_SIMPLE);	// D Key Bind moves camera right
	m_KeyBindManager.bindKey ( createBox, CKeyBind::VK_Z, CKeyBind::BIND_TOGGLE);	// Z Key Bind creates a physics box
	m_KeyBindManager.bindKey ( resetFullscreen, CKeyBind::VK_M, CKeyBind::BIND_TOGGLE);	// M key resets the settings file

	// keys bound to the bump map light source
	m_KeyBindManager.bindKey ( iKeyPressed, CKeyBind::VK_I, CKeyBind::BIND_SIMPLE);
	m_KeyBindManager.bindKey ( kKeyPressed, CKeyBind::VK_K, CKeyBind::BIND_SIMPLE);
	m_KeyBindManager.bindKey ( jKeyPressed, CKeyBind::VK_J, CKeyBind::BIND_SIMPLE);
	m_KeyBindManager.bindKey ( lKeyPressed, CKeyBind::VK_L, CKeyBind::BIND_SIMPLE);
	m_KeyBindManager.bindKey ( oKeyPressed, CKeyBind::VK_O, CKeyBind::BIND_SIMPLE);
	m_KeyBindManager.bindKey ( pKeyPressed, CKeyBind::VK_P, CKeyBind::BIND_SIMPLE);

	// keys bound to the shadow light source
	m_KeyBindManager.bindKey ( fourKeyPressed, (CKeyBind::eKey)VK_NUMPAD4, CKeyBind::BIND_SIMPLE);
	m_KeyBindManager.bindKey ( fiveKeyPressed, (CKeyBind::eKey)VK_NUMPAD5, CKeyBind::BIND_SIMPLE);
	m_KeyBindManager.bindKey ( sixKeyPressed,  (CKeyBind::eKey)VK_NUMPAD6, CKeyBind::BIND_SIMPLE);
	m_KeyBindManager.bindKey ( eightKeyPressed, (CKeyBind::eKey)VK_NUMPAD8, CKeyBind::BIND_SIMPLE);

	// Loading and initializing the MD5 model
	m_pThreadModelLoader = new CThread(&loadModels);
	m_pThreadModelLoader->start();

	m_pLoader->SetProgress(10);

	// Load textures
	m_pTexMirror = m_TextureManager.LoadTexture ( "textures/marble.png");	// wall texture
	m_TextureManager.LoadTexture ( "textures/box1.png");					
	m_TextureManager.LoadTexture ( "textures/box2.png");
	m_TextureManager.LoadTexture ( "textures/box3.png");	// box textures
	m_TextureManager.LoadTexture ( "textures/box4.png");
	m_TextureManager.LoadTexture ( "textures/box5.png");
	m_pLoader->SetProgress(20);

	// initialise per fragment lighting
	m_Lighting.init();

	m_pLoader->SetProgress(30);

	// Create our font
	HFONT font;

	fontBase = glGenLists(96);
	font = CreateFont(-12, 0, 0, 0, FW_BOLD, 0, 0, 0, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE|DEFAULT_PITCH, "Arial");

	HFONT oldfont = oldfont = (HFONT) SelectObject(hDC, font);
	wglUseFontBitmaps(hDC, 32, 96, fontBase);
	SelectObject(hDC, oldfont);
	DeleteObject(font);

	m_TextManager.setFontBase(fontBase);
	m_pTextFPS = m_TextManager.createTextObject(CVector2(-0.75f, 0.55f), CColor(1.0f, 1.0f, 1.0f, 1.0f), "FPS: 0");
	m_pTextPhysics = m_TextManager.createTextObject(CVector2(-0.75f, 0.52f), CColor(1.0f, 1.0f, 1.0f, 1.0f), "Physics Cubes: 0");
	m_pTextPhysicsCollisions = m_TextManager.createTextObject(CVector2(-0.75f, 0.49f), CColor(1.0f, 1.0f, 1.0f, 1.0f), "Physics Collisions: 0");
	m_pTextFullscreen = m_TextManager.createTextObject(CVector2(-0.75f, 0.46f), CColor(1.0f, 1.0f, 1.0f, 1.0f), "Press M to reset Fullscreen Settings");
	m_pTextCameraPosition = m_TextManager.createTextObject(CVector2(-0.75f, 0.43f), CColor(1.0f, 1.0f, 1.0f, 1.0f), "Camera Position: 0, 0, 0");
	
	m_pTextControlsCamera = m_TextManager.createTextObject(CVector2(-0.75f, -0.56f), CColor(1.0f, 1.0f, 1.0f, 1.0f), "Camera Controls: WASD = Movement");
	m_pTextControlsShadows = m_TextManager.createTextObject(CVector2(-0.75f, -0.53f), CColor(1.0f, 1.0f, 1.0f, 1.0f), "Shadow Controls: Numpad 8456 = Movement (WASD Style)");
	m_pTextControlsBumpmap = m_TextManager.createTextObject(CVector2(-0.75f, -0.50f), CColor(1.0f, 1.0f, 1.0f, 1.0f), "Bumpmap Controls: IJKLOP = Movement");
	m_pTextControlsPhysics = m_TextManager.createTextObject(CVector2(-0.75f, -0.47f), CColor(1.0f, 1.0f, 1.0f, 1.0f), "Physics Controls: Z = Spawn Box");


	// Initialize skybox
	m_SkyBox.loadGLTextures();
	
	m_pLoader->SetProgress(40);

	// Physics
	dInitODE();
	m_ODEWorld = dWorldCreate();
	m_ODESpace = dHashSpaceCreate(0);
	dWorldSetGravity(m_ODEWorld, 0, -9.81, 0);
	m_ODEJointGroup = dJointGroupCreate(0);
	m_floorPlane = dCreatePlane(m_ODESpace, 0, 1, 0, 0);
	m_roofPlane = dCreatePlane(m_ODESpace, 0, -1, 0, -45);
	m_wallPlaneNorth = dCreatePlane(m_ODESpace, 1, 0, 0, -13.0);
	m_wallPlaneSouth = dCreatePlane(m_ODESpace, -1, 0, 0, -68.0);
	m_wallPlaneEast = dCreatePlane(m_ODESpace, 0, 0, 1, -82.0);
	m_wallPlaneWest = dCreatePlane(m_ODESpace, 0, 0, -1, -68.0);

	m_pLoader->SetProgress(60);

	// Setting initial camera values
	m_Camera.init(105.0f, -35.0f, 82.0f, 0.0f, 225.0f, 0.0f);

	m_pLoader->SetProgress(85);
	// Initialize our particle emitters
	CColor blood(1.0f, 0.0f, 0.0f, 1.0f);
	m_pParticleEmitter1 = new CParticleEmitter(CVector3(92.0f, -33.3f, 92.0f), CVector3(-0.8f, -0.8f, -0.8f), CVector3(-1.0f, 0.0f, 1.0f), blood);
	m_pParticleEmitter2 = new CParticleEmitter(CVector3(80.3f, -33.3f, 92.0f), CVector3(-0.8f, -0.8f, -0.8f), CVector3(1.0f, 0.0f, 1.0f), blood);

	m_pLoader->SetProgress(95);
	
	// Start our FPS Timer
	tmrFPSLimiter = m_TimerManager.createTimer(resetFPSCounter, 1000, 0, NULL);

	// Royalty Free MP3 Track by Jacek Dojwa from http://www.royalty-free.tv/rftv/free_mp3.htm
	m_pMusic = m_SoundManager.createSound("audio/Automne_Ensoleille.mp3", true, true, 1.0f, CVector3(92.0f, -33.3f, 92.0f), 0.0f, 100.0f);
	createBox();

	m_pLoader->SetProgress(100);
	
	// initialise our reflections
	m_Reflections.init();

	// initialise bump mapping, including setting up the shaders
	m_BumpMapping.init();

	// initialise the shadows light source
	m_Shadows.init();

	unsigned int uiEndTick = GET_TICK();
	this->m_uiLoadTime = uiEndTick - uiStartTick;

	m_bLoaded = true;

	return TRUE;
}

void* CGame::loadModels( void* )
{
	m_Player.loadMesh("models/test/qshambler.md5mesh");
	m_Player.init(92.0f, -39.0f, 92.0f, 270.0f, 0.0f, 190.0f, 0.05f);	// The floor is at -41 on the Y axis. Drawing the model on the floor.
	return NULL;
}

void CGame::resetFullscreen ( void )
{
	if (MessageBox(hWnd, "Are you sure you want to reset your Full screen setting?\n\n You will be prompted regarding full screen at the next game launch.", "Reset Fullscreen?", MB_YESNO|MB_ICONEXCLAMATION) == IDYES)
		remove(SETTINGS_FILENAME);
}

void CGame::handleCollisions ( dGeomID obj0, dGeomID obj1 )
{
	unsigned long colBits0 = dGeomGetCollideBits(obj0);
	unsigned long colBits1 = dGeomGetCollideBits(obj1);

	if((colBits0 | colBits1) < ~0) // part of same higher level object, don't collide
		return;

	// array of contact joints
	static const int MAX_CONTACTS = 10;
	dContact contact [MAX_CONTACTS];

	int numc = dCollide(obj0, obj1, MAX_CONTACTS, &contact[0].geom, sizeof(dContact));
	for(int i = 0; i < numc; i++)
	{
		// setting ode parameters which control collision response
		contact[i].surface.mode =  dContactFDir1 | dContactSlip1 | dContactSlip2 | dContactSoftCFM;
		contact[i].surface.mu = 10.0f;
		contact[i].surface.mu2 = 0;
		contact[i].surface.bounce = 0;
		contact[i].surface.bounce_vel = 0;
		contact[i].surface.soft_cfm = 0.00;
		contact[i].surface.slip1 = 0;
		contact[i].surface.slip2 = 0;
	}
	
	if(numc)
	{
		dBodyID b1 = dGeomGetBody(obj0);
		dBodyID b2 = dGeomGetBody(obj1);
		m_pTextPhysicsCollisions->setText("Physics Collisions: %i", m_cCollisions++);
		for(int i = 0; i < numc; i++)
		{	
			// temporary contact between two objects
			dJointID c = dJointCreateContact(m_ODEWorld, m_ODEJointGroup, contact + i);
			dJointAttach(c, b1, b2);
		}
	}
}

void CGame::nearCallback ( void* data, dGeomID obj0, dGeomID obj1 )
{
	reinterpret_cast < CGame* > (data)->handleCollisions(obj0, obj1);
}

void CGame::updatePhysics ( )
{
	static float fSecondsByStep = 0.0005f; // each step is 0.005 secs
	DWORD msElapsed = m_phyTimer.elapsed();
	float fSecsElapsed = (float)msElapsed / 1000.0f;
	m_phyTimer.restart();


	int iStepsToPerform = static_cast<int>(fSecsElapsed / fSecondsByStep);

	for(int iStep = 0; iStep < iStepsToPerform; iStep++)
	{
		dSpaceCollide(m_ODESpace, 0, &nearCallback);
		dWorldQuickStep(m_ODEWorld, fSecondsByStep);
		dJointGroupEmpty(m_ODEJointGroup); // clear the joint group
	}
}

void CGame::resetFPSCounter ( int iteration, int value )
{
	m_pTextFPS->setText("FPS: %i", m_dwFPS);
	m_dwLastFPS = m_dwFPS;
	m_dwFPS = 0;
}

void CGame::oKeyPressed()
{
	// set the new x position for the bump map light source
	m_BumpMapping.setLightXPos(0.25f);
}

void CGame::pKeyPressed()
{
	// set the new x position for the bump map light source
	m_BumpMapping.setLightXNeg(0.25f);
}

void CGame::iKeyPressed()
{
	// set the new y position for the bump map light source
	m_BumpMapping.setLightYPos(0.25f);
}

void CGame::kKeyPressed()
{
	// set the new y position for the bump map light source
	m_BumpMapping.setLightYNeg(0.25f);
}

void CGame::jKeyPressed()
{
	// set the new z position for the bump map light source
	m_BumpMapping.setLightZPos(0.25f);
}

void CGame::lKeyPressed()
{
	// set the new z position for the bump map light source
	m_BumpMapping.setLightZNeg(0.25f);
}

void CGame::fourKeyPressed()
{
	// set the new x position for the shadow light source
	m_Shadows.setLightXPos(0.025f);
}

void CGame::sixKeyPressed()
{
	// set the new x position for the shadow light source
	m_Shadows.setLightXNeg(0.025f);
}

void CGame::eightKeyPressed()
{
	// set the new y position for the shadow light source
	m_Shadows.setLightYPos(0.025f);
}

void CGame::fiveKeyPressed()
{
	// set the new y position for the shadow light source
	m_Shadows.setLightYNeg(0.025f);
}

void CGame::wKeyPressed()
{
	// Calculations to move camera forwards
	GLfloat fYRotationInRadians = (m_Camera.getYRotation() / 180 * M_PI); // Converting to radians
	GLfloat fXRotationInRadians = (m_Camera.getXRotation() / 180 * M_PI); // Converting to radians
	// Get the current X position
	GLfloat temp = m_Camera.getXPosition();

	if ( temp <= xbound )
		temp = xbound;

	if ( temp >= xboundNeg )
		temp = xboundNeg;

	// Calculate the distance the camera moves and add it to the old X position
	temp += GLfloat(sin(fYRotationInRadians))/2;
	// Set the new X position
	m_Camera.setXPosition(temp);
	// Get the current Z position
	temp = m_Camera.getZPosition();
	// Calculate the distance the camera moves and add it to the old Z position
	temp -= GLfloat(cos(fYRotationInRadians))/2;
	// Set the new Z position
	if ( temp <= zbound )
		temp = zbound;

	if ( temp >= zboundNeg )
		temp = zboundNeg;
	m_Camera.setZPosition(temp);
	// Get the current Y position
	temp = m_Camera.getYPosition();
	// Calculate the distance the camera moves and add it to the old Z position
	temp -= GLfloat(sin(fXRotationInRadians));
	// Set the new Y position
	m_Camera.setYPosition(temp);
}

void CGame::sKeyPressed()
{
	// Calculations to move camera backwards
	GLfloat fYRotationInRadians = (m_Camera.getYRotation() / 180 * M_PI); // Converting to radians
	GLfloat fXRotationInRadians = (m_Camera.getXRotation() / 180 * M_PI); // Converting to radians
	// Get the current X position
	GLfloat temp = m_Camera.getXPosition();

	if ( temp <= xbound )
		temp = xbound;

	if ( temp >= xboundNeg )
		temp = xboundNeg;
	// Calculate the distance the camera moves and add it to the old X position
	temp -= GLfloat(sin(fYRotationInRadians))/2;

	// Set the new X position
	m_Camera.setXPosition(temp);
	// Get the current Z position
	temp = m_Camera.getZPosition();
	// Calculate the distance the camera moves and add it to the old Z position
	temp += GLfloat(cos(fYRotationInRadians))/2;

	if ( temp <= zbound )
		temp = zbound;

	if ( temp >= zboundNeg )
		temp = zboundNeg;
	// Set the new Z position
	m_Camera.setZPosition(temp);
	// Get the current Y position
	temp = m_Camera.getYPosition();
	// Calculate the distance the camera moves and add it to the old Y position
	temp += GLfloat(sin(fXRotationInRadians));
	// Set the new Y position
	m_Camera.setYPosition(temp);
}

void CGame::aKeyPressed()
{
	// Subtract 2.5f from the current Y rotation
	m_Camera.setYRotation(-1.5f);
}

void CGame::dKeyPressed()
{
	// Add 2.5f to the current Y rotation
	m_Camera.setYRotation(1.5f);
}

int CGame::mainLoop ( )
{
	// update game logic based off of fps
	static DWORD dwTimeOfLastFrame = 0;
	m_dwCurrentTime = timeGetTime ();

	if ((m_dwCurrentTime - dwTimeOfLastFrame) < (1000 / m_dwFPSLimit)) // 1000 ms = 1 sec
	{
		return m_iReturnValue;
	}
	dwTimeOfLastFrame = m_dwCurrentTime;

	m_KeyBindManager.Proc();
	m_TimerManager.Proc();

	// update physics
	updatePhysics();
	// update particles
	m_pParticleEmitter1->Update();
	m_pParticleEmitter2->Update();
	

	// Sounds - Update camera position to calculate 3d sounds
	float cx = m_Camera.getXPosition();
	float cy = m_Camera.getYPosition();
	float cz = m_Camera.getZPosition();

	m_pTextCameraPosition->setText("Camera Position: %f, %f, %f", m_Camera.getXPosition(), m_Camera.getYPosition(), m_Camera.getZPosition());

	// TODO: Add camera or vehicle velocity here
	m_SoundManager.updateCameraPosition (CVector3(cx, cy, cz), CVector3(0, 0, 10), CVector3(0, 0, 0));
	// end of sounds

	return m_iReturnValue;
}

void CGame::resizeGLScene ( GLsizei w, GLsizei h )
{
	glViewport ( 0, 0, (GLsizei) w, (GLsizei) h );
	glMatrixMode ( GL_PROJECTION );
	glLoadIdentity ();
	gluPerspective ( 60.0f, (GLfloat)w/(GLfloat)h, 0.1f, 1000024.0f );
	glMatrixMode ( GL_MODELVIEW );
	glLoadIdentity ();
}

void CGame::createBox ( void )
{
	if ( m_ObjList.size() >= PHYSICS_OBJECT_LIMIT) // we have the limit of objects, pop one off the list
	{
		cOdeObject* pLastCube = m_ObjList.back();
		dGeomDestroy(pLastCube->geom);
		dBodyDestroy(pLastCube->body);
		m_ObjList.pop_back();
	}

	const dReal DISABLE_THRESHOLD = 0.1;
	cOdeCube * pNewCube = new cOdeCube(m_ODEWorld, m_ODESpace, 1.5, 1.5, 1.5, 100.0);
	float fXpos = ((float)(rand()%100)/100.0f - 0.5f) * 2.5f;
	float fZpos = ((float)(rand()%100)/100.0f - 0.5f) * 2.5f;
	pNewCube->setPosition(fXpos, 10.0, 1 + fZpos);
	pNewCube->color[0] = (float)rand() / (float)RAND_MAX;
	pNewCube->color[1] = (float)rand() / (float)RAND_MAX;
	pNewCube->color[2] = (float)rand() / (float)RAND_MAX;
	dGeomSetCollideBits (pNewCube->geom, ~0);
	dBodySetAutoDisableLinearThreshold (pNewCube->body, DISABLE_THRESHOLD);
	dBodySetAutoDisableFlag (pNewCube->body, 1);
	m_ObjList.push_front(pNewCube);
	m_pTextPhysics->setText("Physics Cubes: %i", m_ObjList.size());
}

void CGame::renderGLScene ( void )
{
	// TODO: Draw in here
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	glLoadIdentity(); // Reset the Modelview matrix
	//m_SkyBox.render();
	m_Camera.update(m_Camera.getXPosition(), m_Camera.getYPosition(), m_Camera.getZPosition(),
					m_Camera.getXRotation(), m_Camera.getYRotation(), m_Camera.getZRotation());
	

	// roof
	glPushMatrix();
	//glScalef(3.0f, 3.0f, 3.0f);
	glTranslatef(92.0f, -39.0f, 92.0f);	
	glScalef(3.0f, 1.0f, 3.0f);
	glEnable(GL_TEXTURE_2D);
	m_pTexMirror->BindTexture();
	glBegin(GL_QUADS);							// Begin drawing a quad
		glNormal3f(0.0f, 1.0f, 0.0f);				// Normal pointing up
		glTexCoord2f(1.0f, 1.0f);				// Bottom right of texture
		glVertex3f( 25.0f, 45.0f, 25.0f);				// Bottom right corner of floor
		glTexCoord2f(1.0f, 0.0f);				// Top right of texture
		glVertex3f( 25.0f, 45.0f,-25.0f);				// Top right corner of floor
		glTexCoord2f(0.0f, 0.0f);				// Top left of texture
		glVertex3f(-2.0f, 45.0f,-25.0f);				// Top left corner of floor
		glTexCoord2f(0.0f, 1.0f);				// Bottom left of texture
		glVertex3f(-2.0f, 45.0f, 25.0f);				// Bottom left corner of floor
	glEnd();
	glPopMatrix();

	m_pParticleEmitter1->Draw();

	m_TextureManager.FindTexture("textures/marble.png")->BindTexture();	

	// render the shadows and the objects that get shadowed
	glPushMatrix();		
		glTranslatef(142.0f, -32.0f, 30.0f);	
		m_Shadows.render();
	glPopMatrix();

	// the following code deals with the reflections of the model and the particle system
	glPushMatrix();
	glEnable(GL_STENCIL_TEST); // turn the stencil buffer on
	glColorMask(0, 0, 0, 0); // disable writing to the color buffer
	glDisable(GL_DEPTH_TEST); // disable depth testing
	glStencilFunc(GL_ALWAYS, 1, 1); // allways pass the stencil test
	glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); // keep the current value when the z-test fails and set the the value to stencilfunc when it passes

	// draw the floor and wall that are taking reflections
	glPushMatrix();
	glTranslatef(92.0f, -39.0f, 92.0f);		
	
		m_Reflections.drawFloor();	
		m_Reflections.drawWall();
	glPopMatrix();

	glDisable(GL_BLEND); // disable blending
	glColorMask(1, 1, 1, 1); // enable writing to the color buffer
	glEnable(GL_DEPTH_TEST); // enable depth testing
	glStencilFunc(GL_EQUAL, 1, 1); // 
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // keep the current value when the z-test fails and passes

	// draw the models reflection in the floor
	glPushMatrix();		
		glScalef(1.0f, -1.0f, 1.0f);
		glTranslatef(0.0f, 78.0f, 0.0f);
		m_Lighting.modelLighting(m_Player.modelXPosition, m_Player.modelYPosition, m_Player.modelZPosition,
								 m_Player.modelXRotation, m_Player.modelYRotation, m_Player.modelZRotation,
								 m_Player.modelScale);
		m_Player.render();
		m_Lighting.EndProfile();		
	glPopMatrix();

	// draw the models and the particles reflection in the wall
	glPushMatrix();		
		m_pParticleEmitter2->Draw();
		glScalef(-1.0f, 1.0f, 1.0f);
		glTranslatef(-172.0f, 0.0f, 0.0f);
		m_Lighting.modelLighting(m_Player.modelXPosition, m_Player.modelYPosition, m_Player.modelZPosition,
								 m_Player.modelXRotation, m_Player.modelYRotation, m_Player.modelZRotation,
								 m_Player.modelScale);
		m_Player.render();
		m_Lighting.EndProfile();		
	glPopMatrix();

	glDisable(GL_BLEND); // disable blending
	glDisable(GL_STENCIL_TEST);	// turn the stencil buffer off
	
	glEnable(GL_BLEND); // enable blending
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glColor4f(1.0f, 1.0f, 1.0f, 0.5f);	

	// draw the wall and floor again for blending
	glPushMatrix();	
		glEnable(GL_TEXTURE_2D);
		glTranslatef(92.0f, -39.0f, 92.0f);		
		m_Reflections.drawFloor();
		m_Reflections.drawWall();
	glPopMatrix();	

	glDisable(GL_BLEND); // disable blending	
	
	// draw the actual model
	glPushMatrix();		
		m_Lighting.modelLighting(m_Player.modelXPosition, m_Player.modelYPosition, m_Player.modelZPosition,
								 m_Player.modelXRotation, m_Player.modelYRotation, m_Player.modelZRotation,
								 m_Player.modelScale);
		m_Player.render();
		m_Lighting.EndProfile();	
	glPopMatrix();

	// draw physics
	glEnable(GL_TEXTURE_2D);
	glPushMatrix();
	glTranslatef(99.0f, -39.0f, 99.0f);
	for(constObjIter iter = m_ObjList.begin(); iter != m_ObjList.end(); iter++)
		(*iter)->render();
	glPopMatrix();

	glDisable(GL_BLEND);

	// call the bump mapping function
	glPushMatrix();	
		glTranslatef(142.0f, -39.0f, 142.0f);
		m_BumpMapping.renderBumpMap();		
	glPopMatrix();

	m_pLoader->SetAlphaOffset( m_pLoader->GetAlphaOffset() - 0.02 );

	m_TextManager.Draw();

	

	// Lastly -  increase our FPS counter
	m_dwFPS++; 
}

void CGame::shutDown ( void )
{
	for(objIter iter = m_ObjList.begin(); iter != m_ObjList.end(); iter++)
		delete *iter;

	m_ObjList.clear();
	dSpaceDestroy(m_ODESpace);
	dWorldDestroy(m_ODEWorld);
	dCloseODE();
}